package no.rmz.chez.game;

import no.rmz.chez.game.reporters.GameReporter;
import java.io.IOException;

import no.rmz.chez.chess.board.ChessBoard;
import no.rmz.chez.chess.board.Color;
import no.rmz.chez.chess.pieces.ChessPiece;
import no.rmz.chez.evaluators.BoardEvaluator;
import no.rmz.chez.evaluators.FancyEvaluator;
import no.rmz.chez.players.ChessPlayer;
import no.rmz.chez.misc.Timer;

/**
 * A game, implementing basic functionality,
 * but leaving a some of details  be specified
 * by parameters.
 */
final public class Game {


    /**
     * The maximum number of moves allowed in a game.
     */
    public  static final int MAX_NO_OF_HALF_MOVES_IN_GAME  = 100;

    
    /**
     * State variable to remember the (real) time used by the
     * the game.
     */
    private Timer myTimer = new Timer();
    
    
    /**
     * Get the timer object associated with this game.
     */
    public final Timer getMyTimer(){
        return myTimer;
    }
    
    @Override
    /**
     * Override default toString method to provide readable output
     * when debugging.
     */
    public String toString() {
        final StringBuilder result = new StringBuilder();
        result.append("[Game white = ")
                .append(getWhitePlayer())
                .append(", black = ")
                .append(getBlackPlayer())
                .append(", result = ")
                .append(getResult())
                .append(", time = ")
                .append(getMyTimer())
                .append("]");
        return result.toString();
    }
    

    /**
     * When the game has terminated, this will be the
     * result of it.
     */
    private GameResult result = null;




    /**
     * An object that will let us report on the progress of the
     * game.  This may be simply logging to standard output, to
     * a database, or to an interactive game interface.
     */
    private GameReporter reporter;

    /**
     * Get an chessboard with the initial setup.
     */
    private ChessBoard board = new ChessBoard();

    /**
     * The evaluator used by the
     * by game to figure out when to stop game and
     * who won. May or may not be the one used
     * by the players.
     */
    private BoardEvaluator evaluator =
         FancyEvaluator.getInstance();

    /**
     * A white minimax player.
     */
    private final ChessPlayer whitePlayer;

    /**
     * A black minimax player.
     */
    private final ChessPlayer blackPlayer;

    /**
     * The players.  The game engine will cycle between
     * these two players.
     */
    private ChessPlayer [] players;



    /**
     * Generate a new game.
     * @param blackPlayer The black player.
     * @param whitePlayer The white player.
     * @param rep   Reporting the progress of the game.
     */
    public Game(
            final ChessPlayer  blackPlayer,
            final ChessPlayer  whitePlayer,
            final GameReporter rep) {
        this.blackPlayer         = blackPlayer;
        this.whitePlayer         = whitePlayer;
        this.reporter   = rep;
        this.players    = new ChessPlayer[] {whitePlayer, blackPlayer };
    }

      /**
     * Return the result of the game, or null if none
     * has been found yet.
     * @return the result of the game, if known.
     */
    public final GameResult getResult() {
        return result;
    }

    
    /**
     * Run a game.
     *
     */
    public final  void run() {
       
        myTimer.start();
        reporter.setMyTimer(myTimer);
        int noOfMove = 1;
        while (noOfMove++ <= MAX_NO_OF_HALF_MOVES_IN_GAME) {
            final ChessPlayer p = players[(noOfMove - 1) % 2];

            if (evaluator.isTerminal(board)) {
                final Color c = evaluator.getWinningColor(board);
                reporter.handleNormalTermination(c);
                if (c == Color.BLACK) {
                    result = GameResult.BLACK_WIN;
                } else {
                    result = GameResult.WHITE_WIN;
                }
                break;
            }

            final Move m = p.getNextMove(board);
            if (m == null) {
                reporter.reportDeadlock();
                result = GameResult.NO_MORE_MOVES;
                break;
            }

            final ChessPiece piece  = board.getPieceAt(m.getSource());
            board.applyMove(m);
            reporter.logMove(noOfMove, piece, m, board);
            handleStepping();
        }

        if (noOfMove > MAX_NO_OF_HALF_MOVES_IN_GAME) {
            result = GameResult.TOO_MANY_MOVES;
            reporter.reportTie();
        }
        
        myTimer.stop();
        
        reporter.reportResult();
    }


    /**
     * True iff the game requires user input before continuing.
     */
    private static  boolean steppedExecution = false;

    /**
     * Handle stepping, if required.
     * Stays for now, but should be refactored out of the
     * Game class.
     *
     */
    private static  void handleStepping() {
        if (steppedExecution) {
            System.out.println("Press any key to play next move");
            try {
                System.in.read();
            } catch (IOException e) {
                String msg =
                    "Caught IOException while waiting for the any-key";
                System.out.println(msg);
            }
        }
    }

    /**
     * Get the black player from this game.
     * @return the black player
     */
    public final ChessPlayer getBlackPlayer() {
        return blackPlayer;
    }

    /**
     * Get the white player from this game.
     * @return the white player
     */
    public final ChessPlayer getWhitePlayer() {
        return whitePlayer;
    }
}
